﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using UberChess.Searching;
using UberChess.Structure.Move;
using UberChess.Structure.Pieces;
using UberChess.Menu;
using UberChess.UserInput;

namespace UberChess
{
    public enum GameBoardState
    {
        Initializing,
        Playing,
        WhiteWon,
        BlackWon
    } ;

    public enum PlayState
    {
        PlayersTurn,
        ComputersTurn
    } ;

    public class GameBoard
    {
        private Menu.Menu menu;
        public State currentState;
        private Texture2D _spriteSheet;
        private Texture2D _spriteUndo;
        private SpriteFont _font;
        private Vector2 _selected;
        private Vector2 _inFocus;
        private Side _playerSide;
        private bool _multiplayer = false;
        private Game1 game;
        
        private GameBoardState _gameBoardState;
        private PlayState _playState;

        private float _timeSinceLastInput = 0.0f;
        private const float MinTimeBetweenInput = 0.125f;

        private int _exchangeSelection;

        private float _searchTime = 0.0f;
        private float _allowedSearchTime = 15.0f;
        private IterativeDeepening iterativeDeepening;

        private Stack<State> _previousStates;

        public GameBoard(Texture2D spriteSheet, Game1 game, SpriteFont font, Texture2D spriteUndo)
        {
            this.game = game;
            menu = new Menu.Menu(game);

            _font = font;

            currentState = new State();
            currentState.Initialize();

            _spriteSheet = spriteSheet;
            _spriteUndo = spriteUndo;
            _gameBoardState = GameBoardState.Initializing;

            _previousStates = new Stack<State>();
        }

        public void Update(GameTime gameTime)
        {
            switch (_gameBoardState)
            {
                case GameBoardState.Initializing:
                        _selected = new Vector2(-1, -1);
                        _inFocus = new Vector2(-1, -1);

                        if (_multiplayer)
                        {
                            _playerSide = Side.WHITE;
                            _playState = PlayState.PlayersTurn;
                        }
                        else
                        {
                            if (_playerSide == Side.WHITE)
                                _playState = PlayState.PlayersTurn;
                            else
                            {
                                _playState = PlayState.ComputersTurn;
                            }
                        }

                        _exchangeSelection = 0;
                        _gameBoardState = GameBoardState.Playing;
                    break;
                case GameBoardState.Playing:
                    switch(_playState)
                    {
                        case PlayState.PlayersTurn:
                            var keyboardInput = Input.HandleKeyboardInput(Keyboard.GetState());
                            var userInput = Input.HandleMouseInput(Mouse.GetState());
                            _timeSinceLastInput += (float) gameTime.ElapsedGameTime.TotalSeconds;
                            if (_timeSinceLastInput >= MinTimeBetweenInput)
                            {
                                if (keyboardInput == KeyType.Left && _exchangeSelection > 0)
                                    _exchangeSelection--;
                                else if (keyboardInput == KeyType.Right && _exchangeSelection < 3)
                                    _exchangeSelection++;
                                else if (keyboardInput == KeyType.F11)
                                {
                                    //todo fix topmost when fullscreen...below code doesn't work
                                    if (menu.Visible == false)
                                    {
                                        if (game.isFullScreen)
                                        {
                                            menu.TopMost = true;
                                            menu.ShowDialog();
                                        }
                                        {
                                            menu.TopMost = false;
                                            menu.ShowDialog();
                                        }
                                    }
                                }

                                if (userInput.GetType() == typeof(MouseUndoHover))
                                    _inFocus = userInput.GetPosition();

                                if (userInput.GetType() == typeof(MouseUndoClick))
                                {
                                    _inFocus = userInput.GetPosition();
                                    if (_previousStates.Count() >= 2)
                                    {
                                        _previousStates.Pop();
                                        currentState = _previousStates.Pop();
                                    }
                                }

                                if (userInput.GetType() == typeof (MouseClickPosition))
                                {
                                    var position = userInput.GetPosition();

                                    var pieceAtPosition = currentState.GetPieceAtPosition(position);
                                    if (pieceAtPosition.GetType() != typeof (Empty) &&
                                        pieceAtPosition.GetSide() == _playerSide)
                                    {
                                        _selected = userInput.GetPosition();
                                    }
                                    else if (pieceAtPosition.GetType() != typeof (Empty) &&
                                             pieceAtPosition.GetSide() != _playerSide && IsPossibleMove(position))
                                    {
                                        if (_selected != new Vector2(-1, -1))
                                        {
                                            _previousStates.Push((State) Copier.Copy(currentState));
                                            currentState = currentState.CapturePiece(_selected, position,(ExchangeType)_exchangeSelection);
                                            _selected = new Vector2(-1, -1);

                                            IsEndOfGame();
                                            SwitchPlayState();

                                        }
                                    }
                                    else if (pieceAtPosition.GetType() == typeof (Empty) && IsPossibleMove(position))
                                    {
                                        _previousStates.Push((State)Copier.Copy(currentState));
                                        currentState = currentState.MovePiece(_selected, position,
                                                                              (ExchangeType) _exchangeSelection);
                                        _selected = new Vector2(-1, -1);

                                        IsEndOfGame();
                                        SwitchPlayState();
                                    }
                                    else if (pieceAtPosition.GetType() == typeof (Empty) &&
                                             IsSpecialMove(position))
                                    {
                                        _previousStates.Push((State)Copier.Copy(currentState));
                                        currentState = currentState.CastlingMove(_selected, position);
                                        _selected = new Vector2(-1, -1);

                                        IsEndOfGame();
                                        SwitchPlayState();
                                    }

                                }
                                else
                                    _inFocus = userInput.GetPosition();

                                _timeSinceLastInput = 0.0f;
                            }
                            break;
                       case PlayState.ComputersTurn:
                            if(_searchTime < _allowedSearchTime)
                            {
                                if (iterativeDeepening == null)
                                {
                                    iterativeDeepening = new IterativeDeepening(_playerSide, currentState, 5);
                                    iterativeDeepening.Start();
                                }
                                _searchTime += (float)gameTime.ElapsedGameTime.TotalSeconds;
                            }
                            else
                            {
                                iterativeDeepening.StopSearching();
                                _previousStates.Push((State)Copier.Copy(currentState));
                                var result = iterativeDeepening.GetResult();
                                currentState = result;
                                iterativeDeepening = null;
                                _playState = PlayState.PlayersTurn;
                                _searchTime = 0.0f;
                            }

                            break;
                    }
                    break;
                case GameBoardState.WhiteWon:
                    var key = Input.HandleKeyboardInput(Keyboard.GetState());
                    if (key == KeyType.Space) 
                        _gameBoardState = GameBoardState.Initializing;
                    break;
                case GameBoardState.BlackWon:
                    var keyboardState = Input.HandleKeyboardInput(Keyboard.GetState());
                    if (keyboardState == KeyType.Space) 
                        _gameBoardState = GameBoardState.Initializing;
                    break;
            }
        }

        private void SwitchPlayState()
        {
            if (_multiplayer)
                _playerSide = SwitchPlayerside();
            else
            {
                _playState = PlayState.ComputersTurn;
            }
        }

        private void IsEndOfGame()
        {
            if (currentState.CheckForCheckMate(Side.WHITE))
                _gameBoardState = GameBoardState.BlackWon;
            else if (currentState.CheckForCheckMate(Side.BLACK))
                _gameBoardState = GameBoardState.WhiteWon;
        }

        private Side SwitchPlayerside()
        {
            return (_playerSide == Side.WHITE ? Side.BLACK : Side.WHITE);
        }

        public void SetPlayerSide(Side side)
        {
            _playerSide = side;
        }

        public void SetMultiplayer(bool multiplayer)
        {
            _multiplayer = multiplayer;
        }

        private bool IsSpecialMove(Vector2 position)
        {
            if (currentState.IsWithinBounds(_selected))
                return currentState.GetPieceAtPosition(_selected).GetPossibleMoves(currentState).Any(e => e.possibleMove == position && e.isSpecialMove);

            return false;
        }

        private bool IsPossibleMove(Vector2 position)
        {
            if(currentState.IsWithinBounds(_selected))
                return currentState.GetPieceAtPosition(_selected).GetPossibleMoves(currentState).Any(e => e.possibleMove == position && !e.isSpecialMove);

            return false;
        }
        public void Draw(SpriteBatch spriteBatch)
        {
            switch (_gameBoardState)
            {
                case GameBoardState.Playing:
                    DrawCurrentGameBoard(spriteBatch);
                    if(_playState == PlayState.ComputersTurn)
                    {
                        spriteBatch.DrawString(_font, "Calculating next move...", new Vector2(280, 40), Color.Black);
                    }

                    break;
                case GameBoardState.WhiteWon:
                    DrawCurrentGameBoard(spriteBatch);
                    spriteBatch.DrawString(_font, "Game Over - White won!", new Vector2(400, 400), Color.Yellow);
                    break;
                case GameBoardState.BlackWon:
                    DrawCurrentGameBoard(spriteBatch);
                    spriteBatch.DrawString(_font, "Game Over - Black won!", new Vector2(400, 400), Color.Yellow);
                    break;
            }
        }

        private void DrawCurrentGameBoard(SpriteBatch spriteBatch)
        {
            DrawPieces(spriteBatch);

            if(_selected != new Vector2(-1,-1))
            {
                DrawSelection(spriteBatch);
                DrawPossibleMoves(spriteBatch, currentState.GetPieceAtPosition(_selected).GetPossibleMoves(currentState));
            }

            if (_inFocus != new Vector2(-1,-1) && _inFocus != new Vector2(-2, -2) && _inFocus != _selected)
                DrawInFocus(spriteBatch);

            DrawUndoButton(spriteBatch);

            if (currentState.capturedPieces.Count > 0)
                DrawCapturedPieces(spriteBatch);

            DrawExhangeSelection(spriteBatch);
        }

        private void DrawUndoButton(SpriteBatch spriteBatch)
        {
            if(_inFocus == new Vector2(-2, -2))
                spriteBatch.Draw(_spriteUndo, Constants.UNDOBUTTONPOSITION, new Rectangle(0, (int)Constants.UNDOBUTTONWIDTHHEIGHT.Y, (int)Constants.UNDOBUTTONWIDTHHEIGHT.X, (int)Constants.UNDOBUTTONWIDTHHEIGHT.Y), Color.White);
            else
                spriteBatch.Draw(_spriteUndo, Constants.UNDOBUTTONPOSITION, new Rectangle(0,0,100,45), Color.White);
        }

        private void DrawPieces(SpriteBatch spriteBatch)
        {
            for (var i = 0; i < Constants.STATESQUAREWIDTH; i++)
                for (var j = 0; j < Constants.STATESQUAREHEIGHT; j++)
                {
                    if (currentState.state[i, j].GetType() != typeof (Empty))
                        spriteBatch.Draw(_spriteSheet,
                                         new Rectangle(Constants.HORIZONTALOFFSET + i*Constants.PIECEWIDTH,
                                                       Constants.VERTICALOFFSET + j*Constants.PIECEHEIGHT, Constants.PIECEWIDTH,
                                                       Constants.PIECEHEIGHT), currentState.state[i, j].GetSourceRectangle(),
                                         Color.White);
                }
        }

        private void DrawFlipped(SpriteBatch spriteBatch)
        {
            
        }

        private void DrawPossibleMoves(SpriteBatch spriteBatch, List<PossibleMove> possibleMoves)
        {
            foreach (var possibleMove in possibleMoves)
            {
                var sourceRectangle = new Rectangle((possibleMove.isCaptureMove ? 0 : Constants.PIECEWIDTH), Constants.PIECEHEIGHT*7, Constants.PIECEWIDTH, Constants.PIECEHEIGHT);
                var destinationRectangle = new Rectangle((int)(Constants.HORIZONTALOFFSET + possibleMove.possibleMove.X*Constants.PIECEWIDTH), (int)(Constants.VERTICALOFFSET + possibleMove.possibleMove.Y*Constants.PIECEWIDTH), Constants.PIECEWIDTH, Constants.PIECEHEIGHT);
                spriteBatch.Draw(_spriteSheet, destinationRectangle, sourceRectangle, Color.White);
            }
        }

        private void DrawInFocus(SpriteBatch spriteBatch)
        {
            spriteBatch.Draw(_spriteSheet, new Rectangle(Constants.HORIZONTALOFFSET + (int)_inFocus.X * Constants.PIECEWIDTH, Constants.VERTICALOFFSET + (int)_inFocus.Y * Constants.PIECEHEIGHT, Constants.PIECEWIDTH, Constants.PIECEHEIGHT), new Rectangle(0, Constants.PIECEHEIGHT * 6, Constants.PIECEWIDTH, Constants.PIECEHEIGHT), Color.White);      
        }

        private void DrawSelection(SpriteBatch spriteBatch)
        {
            spriteBatch.Draw(_spriteSheet, new Rectangle(Constants.HORIZONTALOFFSET + (int)_selected.X * Constants.PIECEWIDTH, Constants.VERTICALOFFSET + (int)_selected.Y * Constants.PIECEHEIGHT, Constants.PIECEWIDTH, Constants.PIECEHEIGHT), new Rectangle(Constants.PIECEWIDTH, Constants.PIECEHEIGHT * 6, Constants.PIECEWIDTH, Constants.PIECEHEIGHT), Color.White);
        }

        private void DrawExhangeSelection(SpriteBatch spriteBatch)
        {
            spriteBatch.Draw(_spriteSheet, Constants.ExchangeSelectionDrawingPoints[_exchangeSelection], new Rectangle(Constants.PIECEWIDTH*2, 0, Constants.PIECEWIDTH, Constants.PIECEHEIGHT), Color.White);
        }

        public void DrawCapturedPieces(SpriteBatch spriteBatch)
        {
            var _capturedPieces = currentState.capturedPieces;
            if(_capturedPieces.Count > 0)
            {
                var capturedBlackPieces = _capturedPieces.Where(e => e.GetSide() == Side.BLACK).ToArray();
                for(int i = 0; i < capturedBlackPieces.Count(); i++)
                {
                    var destination = new Vector2(Constants.CAPTUREDBYWHITESTARTDRAWPOSITION.X +i%8 * Constants.PIECEWIDTH,
                                                  Constants.CAPTUREDBYWHITESTARTDRAWPOSITION.Y + (i/8) * Constants.PIECEHEIGHT);
                    var sourceRectangle = capturedBlackPieces[i].GetSourceRectangle();
                    spriteBatch.Draw(_spriteSheet, destination, sourceRectangle, Color.White);
                }

                var capturedWhitePieces = _capturedPieces.Where(e => e.GetSide() == Side.WHITE).ToArray();
                for (int i = 0; i < capturedWhitePieces.Count(); i++)
                {
                    var destination = new Vector2(Constants.CAPTUREDBYBLACKSTARTDRAWPOSITION.X + i%7 * Constants.PIECEWIDTH,
                                                  Constants.CAPTUREDBYBLACKSTARTDRAWPOSITION.Y + (i/7) * Constants.PIECEHEIGHT);
                    var sourceRectangle = capturedWhitePieces[i].GetSourceRectangle();
                    spriteBatch.Draw(_spriteSheet, destination, sourceRectangle, Color.White);
                   
                }
            }
        }


    }
}
